<template>
  <div>
    <el-table
      v-bind="$attrs"
      :data="pageData"
      v-on="$listeners">
      <el-table-column
        label="编辑"
        align="center"
        fixed="left"
        width="70"
      >
        <template slot-scope="prop">
            <div class="action-cell" v-show="!_isEditState(prop.row)">
              <i @click="editRow(prop.row, prop.$index)" class="icon el-icon-edit"></i>
            </div>
            <div class="action-cell" v-show="_isEditState(prop.row)">
              <i class="icon el-icon-check" @click="commitRow(prop.row, prop.$index)"></i>
              <i class="icon el-icon-close" @click="cancelEdit(prop.$index)"></i>
            </div>
        </template>
      </el-table-column>
      <el-table-column
        v-for="( option,index ) in columnOptions"
        v-bind="option"
        :formatter="( row, column, cellValue ) => formatterFn( row, column, cellValue, option )"
      >
      </el-table-column>
    </el-table>
    <el-pagination
      v-if="pagination"
      :current-page.sync="currentPage"
      class="pagination"
      background
      :page-size="pageSize"
      layout="prev, pager, next"
      :total="data.length">
    </el-pagination>
  </div>
</template>

<script type="text/jsx">
  import {pageData, deepClone as _deepClone} from 'common/js/util'

  export default {
    created() {
      this.dataCache = _deepClone(this.data)
    },
    props: {
      data: {
        type: Array,
        default: () => []
      },
      columnOptions: {
        type: Array,
        default: () => []
      },
      pagination: Boolean,
      pageSize: {
        type: Number,
        default: 10
      }
    },
    watch: {
      currentPage() {
        this.data.forEach(val => {
          val.editState = false
        })
      }
    },
    data() {
      return {
        // 处于编辑状态的行
        editStateRows: [],
        // 页数
        currentPage: 1
      }
    },
    methods: {
      // 格式化单元格
      formatterFn(row, column, cellValue, option) {
        // 普通模式显示在cell内的值
        let normalRender
        // 继承父元素传入的formatter jsx
        if (option.formatter) {
          normalRender = option.formatter(row, column, cellValue)
        } else {
          normalRender = cellValue
        }
        // 该行处于编辑状态 并且prop传入的option指定这行是可编辑
        if (row.editState && option.editable) {
          return this._editRender({row, column, cellValue, option})
        } else {
          return normalRender
        }
      },
      // 点开编辑
      editRow(row, index) {
        if (!this.rowCacheMap) {
          this.rowCacheMap = []
        }
        this.rowCacheMap[index] = Object.assign({}, row)
        if (row.editState) return
        if (!row.editState) this.$set(row, 'editState', true)
      },
      // 确认提交编辑行方法
      commitRow(row, index) {
        const refs = this._needValideteRefs(index)
        // 记录校验成功的次数
        let validateNum = 0
        let inValidLabelArr = []
        for (let i = 0, len = refs.length; i < len; i++) {
          let ref = refs[i].ref,
              label = refs[i].label,
              prop = refs[i].prop
          const formItem = this.$refs[ref]
          formItem.validate((valid) => {
            if (valid) {
              validateNum ++
              if (validateNum === refs.length) {
                // 全部校验成功 外部才能接收到事件
                this.$emit('row-commit', {row: this.pageData[index], index})
                row.editState = false
                row.isNewRow && (row.isNewRow  = false)
              }
            }else {
              inValidLabelArr.push(label)
            }
          })
        }
        if (validateNum !== refs.length) {
          this.$message.error(inValidLabelArr.join('、') + '列未通过校验')
        }
      },
      cancelEdit(index) {
        const dataRow = this.pageData[index]
        // 处于新增编辑状态的行
        if (dataRow.isNewRow) {
          this.data.splice(index, 1)
        }else {
          dataRow.editState = false
          const cacheRow = this.rowCacheMap[index]
          const keys = Object.keys(cacheRow)
          keys.forEach(key => {
            dataRow[key] = cacheRow[key]
          })
        }
      },
      // 新增待编辑行
      addRow(row = {}) {
        const hasUnsavedData = this.pageData.some(val => val.editState)
        if (hasUnsavedData) {
          this.$message.warning('有未保存的数据，请先完成填写。')
          return
        }
        this.data.unshift(row)
        this.$set(row, 'editState', true)
        this.$set(row, 'isNewRow', true)
        if (this.currentPage !== 1) {
          this.currentPage = 1
        }
      },
      _isEditState(row) {
        return row.editState
      },
      // 点击确认的那列需要校验的表单ref数组
      _needValideteRefs(index) {
        const options = this.columnOptions
        let refs = []
        Object.keys(options).forEach(key => {
          const option = options[key]
          if (option.rules && option.rules.length > 0) {
            refs.push({
              ref: option.prop + index,
              label: option.label,
              prop: option.prop
            })
          }
        })
        return refs
      },
      // 生成表格内编辑控件的核心方法
      _editRender({ row, column, cellValue, option }) {

        // 生成表单内元素的方法
        const renderFormItem = () => {
          let {editRender, editType, prop, options} = option
          // 用户自定义表单控件
          if (editRender) {
            return editRender(this.pageData[row.$index], row.$index)
          }
          // 默认提供的表单控件
          options = options || {}
          const { onChange , disabled } = options

          const handleChange = (val) => {
            row[prop] = val
            onChange && onChange(val, row)
          }

          switch (editType) {
            case 'el-date-picker' : {

              const { props , style, on } = options
              const finalOptions = {
                props: {
                  type:"date",
                  size:"mini",
                  placeholder:"请选择日期",
                  value: row[prop],
                  format:"yyyy年MM月dd日",
                  'value-format': "yyyy-MM-dd",
                  ...props
                },
                style: {
                  width: '100%',
                  ...style
                },
                on: {
                  input: val => {
                    handleChange(val)
                  },
                  ...on
                }
              }
              return h(
                'el-date-picker', {
                  props: finalOptions.props,
                  style: finalOptions.style,
                  on: finalOptions.on
                }
              )
            }

            case 'el-select' : {

              const { selectData,selectLabel,selectValue,renderLabel } = options
              return (
                <el-select
                  value={row[prop]}
                  onChange={handleChange}
                  style= {{width : '100%'}}
                  filterable
                  disabled={disabled}
                  placeholder="请选择"
                  size="mini">
                  {selectData.map(t => {
                    let label = t[selectLabel || 'label']
                    let value = t[selectValue || 'value'] || label
                    if ( renderLabel ) {
                      label = renderLabel(label,value)
                    }
                    return (
                      <el-option
                        label={label}
                        value={value}
                      >
                      </el-option>
                    )
                  })}
                </el-select>
              )
            }

            default : {

              // 默认为输入框
              const handleInput = (val) => {
                row[prop] = val
              }

              const handleBlur = (e) => {
                onChange && onChange(e.target.value, row)
              }

              return (
                <el-input size="mini"
                          onInput={val => handleInput(val)}
                          onBlur={handleBlur}
                          disabled={disabled}
                          value={cellValue}>
                </el-input>
              )
            }
          }
        }

        // 用el-form包裹表单元素 做校验
        const withElForm = (formItem) => {
          const { prop, rules } = option
          return (
            <el-form inline-message={true}
                     validateOnRuleChange={false}
                     rules={ {[prop] : rules} }
                     model={ row }
                     ref={ prop + row.$index }>
              <el-form-item prop={prop}>
                { formItem }
              </el-form-item>
            </el-form>
          )
        }

        // 返回el-form+生成的form-item
        return withElForm(renderFormItem())
      }
    },
    watch: {
      data: {
        handler(newVal) {
          const editRowsCache = _deepClone(this.editStateRows)
          this.pageData.forEach((val, index) => {
            val.$index = index
          })
          this.editStateRows = newVal.filter(data => data.editState)
          if ( editRowsCache !== this.editStateRows.length) {
            this.$emit('edit-rows-change', this.editStateRows)
          }
        },
        deep: true
      },
      currentPage() {
        this.data = this.data.filter(val => {
          return !val.isNewRow
        })
        this.data.forEach(val => {
          if (val.editState) {
            val.editState = false
          }
        })
      }
    },
    computed: {
      pageData() {
        return this.pagination ? pageData(this.data, this.currentPage, this.pageSize) : this.data
      }
    }
  }
</script>

<style lang="stylus" rel="stylesheet/stylus" scoped>
  .action-cell
    text-align center
    white-space nowrap
    display flex
    justify-content space-around
    .icon
      font-size 17px
      cursor pointer
      transform color .5s
    .el-icon-check
      &:hover
        color #67C23A
    .el-icon-close
      &:hover
        color #F56C6C
    .el-icon-edit
      &:hover
        color #409EFF
.pagination
  float right
  margin-top 10px

>>>.el-form-item
      margin 0px!important
>>>.el-form-item__error
      position absolute
      padding 0
      right 10px
      top 50%
      transform translateY(-50%)
</style>
